/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#include"gc/Statistics.h"#include"mozilla/ArrayUtils.h"#include"mozilla/DebugOnly.h"#include"mozilla/IntegerRange.h"#include"mozilla/PodOperations.h"#include"mozilla/Sprintf.h"#include"mozilla/TimeStamp.h"#include<ctype.h>#include<stdarg.h>#include<stdio.h>#include"jsprf.h"#include"jsutil.h"#include"gc/Memory.h"#include"vm/Debugger.h"#include"vm/HelperThreads.h"#include"vm/Runtime.h"#include"vm/Time.h"usingnamespacejs;usingnamespacejs::gc;usingnamespacejs::gcstats;usingmozilla::DebugOnly;usingmozilla::EnumeratedArray;usingmozilla::IntegerRange;usingmozilla::PodArrayZero;usingmozilla::PodZero;usingmozilla::TimeStamp;usingmozilla::TimeDuration;/* * If this fails, then you can either delete this assertion and allow all * larger-numbered reasons to pile up in the last telemetry bucket, or switch * to GC_REASON_3 and bump the max value. */JS_STATIC_ASSERT(JS::gcreason::NUM_TELEMETRY_REASONS>=JS::gcreason::NUM_REASONS);staticinlinedecltype(mozilla::MakeEnumeratedRange(PhaseKind::FIRST,PhaseKind::LIMIT))AllPhaseKinds(){returnmozilla::MakeEnumeratedRange(PhaseKind::FIRST,PhaseKind::LIMIT);}constchar*js::gcstats::ExplainInvocationKind(JSGCInvocationKindgckind){MOZ_ASSERT(gckind==GC_NORMAL||gckind==GC_SHRINK);if(gckind==GC_NORMAL)return"Normal";elsereturn"Shrinking";}JS_PUBLIC_API(constchar*)JS::gcreason::ExplainReason(JS::gcreason::Reasonreason){switch(reason){#define SWITCH_REASON(name) \ case JS::gcreason::name: \ return #name;GCREASONS(SWITCH_REASON)default:MOZ_CRASH("bad GC reason");#undef SWITCH_REASON}}constchar*js::gcstats::ExplainAbortReason(gc::AbortReasonreason){switch(reason){#define SWITCH_REASON(name) \ case gc::AbortReason::name: \ return #name;GC_ABORT_REASONS(SWITCH_REASON)default:MOZ_CRASH("bad GC abort reason");#undef SWITCH_REASON}}structPhaseKindInfo{PhasefirstPhase;uint8_ttelemetryBucket;};// PhaseInfo objects form a tree.structPhaseInfo{Phaseparent;PhasefirstChild;PhasenextSibling;PhasenextInPhase;PhaseKindphaseKind;uint8_tdepth;constchar*name;constchar*path;};// A table of PhaseInfo indexed by Phase.usingPhaseTable=EnumeratedArray<Phase,Phase::LIMIT,PhaseInfo>;// A table of PhaseKindInfo indexed by PhaseKind.usingPhaseKindTable=EnumeratedArray<PhaseKind,PhaseKind::LIMIT,PhaseKindInfo>;#include"gc/StatsPhasesGenerated.cpp"staticdoublet(TimeDurationduration){returnduration.ToMilliseconds();}inlinePhaseStatistics::currentPhase()const{returnphaseStack.empty()?Phase::NONE:phaseStack.back();}PhaseKindStatistics::currentPhaseKind()const{// Public API to get the current phase kind, suppressing the synthetic// PhaseKind::MUTATOR phase.Phasephase=currentPhase();MOZ_ASSERT_IF(phase==Phase::MUTATOR,phaseStack.length()==1);if(phase==Phase::NONE||phase==Phase::MUTATOR)returnPhaseKind::NONE;returnphases[phase].phaseKind;}PhaseStatistics::lookupChildPhase(PhaseKindphaseKind)const{if(phaseKind==PhaseKind::IMPLICIT_SUSPENSION)returnPhase::IMPLICIT_SUSPENSION;if(phaseKind==PhaseKind::EXPLICIT_SUSPENSION)returnPhase::EXPLICIT_SUSPENSION;MOZ_ASSERT(phaseKind<PhaseKind::LIMIT);// Most phases only correspond to a single expanded phase so check for that// first.Phasephase=phaseKinds[phaseKind].firstPhase;if(phases[phase].nextInPhase==Phase::NONE){MOZ_ASSERT(phases[phase].parent==currentPhase());returnphase;}// Otherwise search all expanded phases that correspond to the required// phase to find the one whose parent is the current expanded phase.Phaseparent=currentPhase();while(phases[phase].parent!=parent){phase=phases[phase].nextInPhase;MOZ_ASSERT(phase!=Phase::NONE);}returnphase;}inlinedecltype(mozilla::MakeEnumeratedRange(Phase::FIRST,Phase::LIMIT))AllPhases(){returnmozilla::MakeEnumeratedRange(Phase::FIRST,Phase::LIMIT);}voidStatistics::gcDuration(TimeDuration*total,TimeDuration*maxPause)const{*total=*maxPause=0;for(auto&slice:slices_){*total+=slice.duration();if(slice.duration()>*maxPause)*maxPause=slice.duration();}if(*maxPause>maxPauseInInterval)maxPauseInInterval=*maxPause;}voidStatistics::sccDurations(TimeDuration*total,TimeDuration*maxPause)const{*total=*maxPause=0;for(size_ti=0;i<sccTimes.length();i++){*total+=sccTimes[i];*maxPause=Max(*maxPause,sccTimes[i]);}}typedefVector<UniqueChars,8,SystemAllocPolicy>FragmentVector;staticUniqueCharsJoin(constFragmentVector&fragments,constchar*separator=""){constsize_tseparatorLength=strlen(separator);size_tlength=0;for(size_ti=0;i<fragments.length();++i){length+=fragments[i]?strlen(fragments[i].get()):0;if(i<(fragments.length()-1))length+=separatorLength;}char*joined=js_pod_malloc<char>(length+1);if(!joined)returnUniqueChars();joined[length]='\0';char*cursor=joined;for(size_ti=0;i<fragments.length();++i){if(fragments[i])strcpy(cursor,fragments[i].get());cursor+=fragments[i]?strlen(fragments[i].get()):0;if(i<(fragments.length()-1)){if(separatorLength)strcpy(cursor,separator);cursor+=separatorLength;}}returnUniqueChars(joined);}staticTimeDurationSumChildTimes(Phasephase,constStatistics::PhaseTimeTable&phaseTimes){TimeDurationtotal=0;for(phase=phases[phase].firstChild;phase!=Phase::NONE;phase=phases[phase].nextSibling){total+=phaseTimes[phase];}returntotal;}UniqueCharsStatistics::formatCompactSliceMessage()const{// Skip if we OOM'ed.if(slices_.length()==0)returnUniqueChars(nullptr);constsize_tindex=slices_.length()-1;constSliceData&slice=slices_.back();charbudgetDescription[200];slice.budget.describe(budgetDescription,sizeof(budgetDescription)-1);constchar*format="GC Slice %u - Pause: %.3fms of %s budget (@ %.3fms); Reason: %s; Reset: %s%s; Times: ";charbuffer[1024];SprintfLiteral(buffer,format,index,t(slice.duration()),budgetDescription,t(slice.start-slices_[0].start),ExplainReason(slice.reason),slice.wasReset()?"yes - ":"no",slice.wasReset()?ExplainAbortReason(slice.resetReason):"");FragmentVectorfragments;if(!fragments.append(DuplicateString(buffer))||!fragments.append(formatCompactSlicePhaseTimes(slices_[index].phaseTimes))){returnUniqueChars(nullptr);}returnJoin(fragments);}UniqueCharsStatistics::formatCompactSummaryMessage()const{constdoublebytesPerMiB=1024*1024;FragmentVectorfragments;if(!fragments.append(DuplicateString("Summary - ")))returnUniqueChars(nullptr);TimeDurationtotal,longest;gcDuration(&total,&longest);constdoublemmu20=computeMMU(TimeDuration::FromMilliseconds(20));constdoublemmu50=computeMMU(TimeDuration::FromMilliseconds(50));charbuffer[1024];if(!nonincremental()){SprintfLiteral(buffer,"Max Pause: %.3fms; MMU 20ms: %.1f%%; MMU 50ms: %.1f%%; Total: %.3fms; ",t(longest),mmu20*100.,mmu50*100.,t(total));}else{SprintfLiteral(buffer,"Non-Incremental: %.3fms (%s); ",t(total),ExplainAbortReason(nonincrementalReason_));}if(!fragments.append(DuplicateString(buffer)))returnUniqueChars(nullptr);SprintfLiteral(buffer,"Zones: %d of %d (-%d); Compartments: %d of %d (-%d); HeapSize: %.3f MiB; " \"HeapChange (abs): %+d (%d); ",zoneStats.collectedZoneCount,zoneStats.zoneCount,zoneStats.sweptZoneCount,zoneStats.collectedCompartmentCount,zoneStats.compartmentCount,zoneStats.sweptCompartmentCount,double(preBytes)/bytesPerMiB,counts[STAT_NEW_CHUNK]-counts[STAT_DESTROY_CHUNK],counts[STAT_NEW_CHUNK]+counts[STAT_DESTROY_CHUNK]);if(!fragments.append(DuplicateString(buffer)))returnUniqueChars(nullptr);MOZ_ASSERT_IF(counts[STAT_ARENA_RELOCATED],gckind==GC_SHRINK);if(gckind==GC_SHRINK){SprintfLiteral(buffer,"Kind: %s; Relocated: %.3f MiB; ",ExplainInvocationKind(gckind),double(ArenaSize*counts[STAT_ARENA_RELOCATED])/bytesPerMiB);if(!fragments.append(DuplicateString(buffer)))returnUniqueChars(nullptr);}returnJoin(fragments);}UniqueCharsStatistics::formatCompactSlicePhaseTimes(constPhaseTimeTable&phaseTimes)const{staticconstTimeDurationMaxUnaccountedTime=TimeDuration::FromMicroseconds(100);FragmentVectorfragments;charbuffer[128];for(autophase:AllPhases()){DebugOnly<uint8_t>level=phases[phase].depth;MOZ_ASSERT(level<4);TimeDurationownTime=phaseTimes[phase];TimeDurationchildTime=SumChildTimes(phase,phaseTimes);if(ownTime>MaxUnaccountedTime){SprintfLiteral(buffer,"%s: %.3fms",phases[phase].name,t(ownTime));if(!fragments.append(DuplicateString(buffer)))returnUniqueChars(nullptr);if(childTime&&(ownTime-childTime)>MaxUnaccountedTime){MOZ_ASSERT(level<3);SprintfLiteral(buffer,"%s: %.3fms","Other",t(ownTime-childTime));if(!fragments.append(DuplicateString(buffer)))returnUniqueChars(nullptr);}}}returnJoin(fragments,", ");}UniqueCharsStatistics::formatDetailedMessage()const{FragmentVectorfragments;if(!fragments.append(formatDetailedDescription()))returnUniqueChars(nullptr);if(!slices_.empty()){for(unsignedi=0;i<slices_.length();i++){if(!fragments.append(formatDetailedSliceDescription(i,slices_[i])))returnUniqueChars(nullptr);if(!fragments.append(formatDetailedPhaseTimes(slices_[i].phaseTimes)))returnUniqueChars(nullptr);}}if(!fragments.append(formatDetailedTotals()))returnUniqueChars(nullptr);if(!fragments.append(formatDetailedPhaseTimes(phaseTimes)))returnUniqueChars(nullptr);returnJoin(fragments);}UniqueCharsStatistics::formatDetailedDescription()const{constdoublebytesPerMiB=1024*1024;TimeDurationsccTotal,sccLongest;sccDurations(&sccTotal,&sccLongest);constdoublemmu20=computeMMU(TimeDuration::FromMilliseconds(20));constdoublemmu50=computeMMU(TimeDuration::FromMilliseconds(50));constchar*format="=================================================================\n\ Invocation Kind: %s\n\ Reason: %s\n\ Incremental: %s%s\n\ Zones Collected: %d of %d (-%d)\n\ Compartments Collected: %d of %d (-%d)\n\ MinorGCs since last GC: %d\n\ Store Buffer Overflows: %d\n\ MMU 20ms:%.1f%%; 50ms:%.1f%%\n\ SCC Sweep Total (MaxPause): %.3fms (%.3fms)\n\ HeapSize: %.3f MiB\n\ Chunk Delta (magnitude): %+d (%d)\n\ Arenas Relocated: %.3f MiB\n\";charbuffer[1024];SprintfLiteral(buffer,format,ExplainInvocationKind(gckind),ExplainReason(slices_[0].reason),nonincremental()?"no - ":"yes",nonincremental()?ExplainAbortReason(nonincrementalReason_):"",zoneStats.collectedZoneCount,zoneStats.zoneCount,zoneStats.sweptZoneCount,zoneStats.collectedCompartmentCount,zoneStats.compartmentCount,zoneStats.sweptCompartmentCount,getCount(STAT_MINOR_GC),getCount(STAT_STOREBUFFER_OVERFLOW),mmu20*100.,mmu50*100.,t(sccTotal),t(sccLongest),double(preBytes)/bytesPerMiB,getCount(STAT_NEW_CHUNK)-getCount(STAT_DESTROY_CHUNK),getCount(STAT_NEW_CHUNK)+getCount(STAT_DESTROY_CHUNK),double(ArenaSize*getCount(STAT_ARENA_RELOCATED))/bytesPerMiB);returnDuplicateString(buffer);}UniqueCharsStatistics::formatDetailedSliceDescription(unsignedi,constSliceData&slice)const{charbudgetDescription[200];slice.budget.describe(budgetDescription,sizeof(budgetDescription)-1);constchar*format="\ ---- Slice %u ----\n\ Reason: %s\n\ Reset: %s%s\n\ State: %s -> %s\n\ Page Faults: %ld\n\ Pause: %.3fms of %s budget (@ %.3fms)\n\";charbuffer[1024];SprintfLiteral(buffer,format,i,ExplainReason(slice.reason),slice.wasReset()?"yes - ":"no",slice.wasReset()?ExplainAbortReason(slice.resetReason):"",gc::StateName(slice.initialState),gc::StateName(slice.finalState),uint64_t(slice.endFaults-slice.startFaults),t(slice.duration()),budgetDescription,t(slice.start-slices_[0].start));returnDuplicateString(buffer);}UniqueCharsStatistics::formatDetailedPhaseTimes(constPhaseTimeTable&phaseTimes)const{staticconstTimeDurationMaxUnaccountedChildTime=TimeDuration::FromMicroseconds(50);FragmentVectorfragments;charbuffer[128];for(autophase:AllPhases()){uint8_tlevel=phases[phase].depth;TimeDurationownTime=phaseTimes[phase];TimeDurationchildTime=SumChildTimes(phase,phaseTimes);if(!ownTime.IsZero()){SprintfLiteral(buffer," %*s: %.3fms\n",level*2,phases[phase].name,t(ownTime));if(!fragments.append(DuplicateString(buffer)))returnUniqueChars(nullptr);if(childTime&&(ownTime-childTime)>MaxUnaccountedChildTime){SprintfLiteral(buffer," %*s: %.3fms\n",(level+1)*2,"Other",t(ownTime-childTime));if(!fragments.append(DuplicateString(buffer)))returnUniqueChars(nullptr);}}}returnJoin(fragments);}UniqueCharsStatistics::formatDetailedTotals()const{TimeDurationtotal,longest;gcDuration(&total,&longest);constchar*format="\ ---- Totals ----\n\ Total Time: %.3fms\n\ Max Pause: %.3fms\n\";charbuffer[1024];SprintfLiteral(buffer,format,t(total),t(longest));returnDuplicateString(buffer);}voidStatistics::formatJsonSlice(size_tsliceNum,JSONPrinter&json)const{json.beginObject();formatJsonSliceDescription(sliceNum,slices_[sliceNum],json);json.beginObjectProperty("times");formatJsonPhaseTimes(slices_[sliceNum].phaseTimes,json);json.endObject();json.endObject();}UniqueCharsStatistics::renderJsonSlice(size_tsliceNum)const{Sprinterprinter(nullptr,false);if(!printer.init())returnUniqueChars(nullptr);JSONPrinterjson(printer);formatJsonSlice(sliceNum,json);returnUniqueChars(printer.release());}UniqueCharsStatistics::renderNurseryJson(JSRuntime*rt)const{Sprinterprinter(nullptr,false);if(!printer.init())returnUniqueChars(nullptr);JSONPrinterjson(printer);rt->gc.nursery().renderProfileJSON(json);returnUniqueChars(printer.release());}UniqueCharsStatistics::renderJsonMessage(uint64_ttimestamp,boolincludeSlices)const{if(aborted)returnDuplicateString("{status:\"aborted\"}");// May return nullptrSprinterprinter(nullptr,false);if(!printer.init())returnUniqueChars(nullptr);JSONPrinterjson(printer);json.beginObject();formatJsonDescription(timestamp,json);if(includeSlices){json.beginListProperty("slices");for(unsignedi=0;i<slices_.length();i++)formatJsonSlice(i,json);json.endList();}json.beginObjectProperty("totals");formatJsonPhaseTimes(phaseTimes,json);json.endObject();json.endObject();returnUniqueChars(printer.release());}voidStatistics::formatJsonDescription(uint64_ttimestamp,JSONPrinter&json)const{json.property("timestamp",timestamp);TimeDurationtotal,longest;gcDuration(&total,&longest);json.property("max_pause",longest,JSONPrinter::MILLISECONDS);json.property("total_time",total,JSONPrinter::MILLISECONDS);json.property("reason",ExplainReason(slices_[0].reason));json.property("zones_collected",zoneStats.collectedZoneCount);json.property("total_zones",zoneStats.zoneCount);json.property("total_compartments",zoneStats.compartmentCount);json.property("minor_gcs",counts[STAT_MINOR_GC]);json.property("store_buffer_overflows",counts[STAT_STOREBUFFER_OVERFLOW]);json.property("slices",slices_.length());constdoublemmu20=computeMMU(TimeDuration::FromMilliseconds(20));constdoublemmu50=computeMMU(TimeDuration::FromMilliseconds(50));json.property("mmu_20ms",int(mmu20*100));json.property("mmu_50ms",int(mmu50*100));TimeDurationsccTotal,sccLongest;sccDurations(&sccTotal,&sccLongest);json.property("scc_sweep_total",sccTotal,JSONPrinter::MILLISECONDS);json.property("scc_sweep_max_pause",sccLongest,JSONPrinter::MILLISECONDS);json.property("nonincremental_reason",ExplainAbortReason(nonincrementalReason_));json.property("allocated",uint64_t(preBytes)/1024/1024);json.property("added_chunks",getCount(STAT_NEW_CHUNK));json.property("removed_chunks",getCount(STAT_DESTROY_CHUNK));json.property("major_gc_number",startingMajorGCNumber);json.property("minor_gc_number",startingMinorGCNumber);}voidStatistics::formatJsonSliceDescription(unsignedi,constSliceData&slice,JSONPrinter&json)const{TimeDurationwhen=slice.start-slices_[0].start;charbudgetDescription[200];slice.budget.describe(budgetDescription,sizeof(budgetDescription)-1);TimeStamporiginTime=TimeStamp::ProcessCreation();json.property("slice",i);json.property("pause",slice.duration(),JSONPrinter::MILLISECONDS);json.property("when",when,JSONPrinter::MILLISECONDS);json.property("reason",ExplainReason(slice.reason));json.property("initial_state",gc::StateName(slice.initialState));json.property("final_state",gc::StateName(slice.finalState));json.property("budget",budgetDescription);json.property("page_faults",int64_t(slice.endFaults-slice.startFaults));json.property("start_timestamp",slice.start-originTime,JSONPrinter::SECONDS);json.property("end_timestamp",slice.end-originTime,JSONPrinter::SECONDS);}voidStatistics::formatJsonPhaseTimes(constPhaseTimeTable&phaseTimes,JSONPrinter&json)const{for(autophase:AllPhases()){TimeDurationownTime=phaseTimes[phase];if(!ownTime.IsZero())json.property(phases[phase].path,ownTime,JSONPrinter::MILLISECONDS);}}Statistics::Statistics(JSRuntime*rt):runtime(rt),fp(nullptr),nonincrementalReason_(gc::AbortReason::None),preBytes(0),maxPauseInInterval(0),sliceCallback(nullptr),nurseryCollectionCallback(nullptr),aborted(false),enableProfiling_(false),sliceCount_(0){for(auto&count:counts)count=0;PodZero(&totalTimes_);MOZ_ALWAYS_TRUE(phaseStack.reserve(MAX_PHASE_NESTING));MOZ_ALWAYS_TRUE(suspendedPhases.reserve(MAX_SUSPENDED_PHASES));constchar*env=getenv("MOZ_GCTIMER");if(env){if(strcmp(env,"none")==0){fp=nullptr;}elseif(strcmp(env,"stdout")==0){fp=stdout;}elseif(strcmp(env,"stderr")==0){fp=stderr;}else{fp=fopen(env,"a");if(!fp)MOZ_CRASH("Failed to open MOZ_GCTIMER log file.");}}env=getenv("JS_GC_PROFILE");if(env){if(0==strcmp(env,"help")){fprintf(stderr,"JS_GC_PROFILE=N\n""\tReport major GC's taking more than N milliseconds.\n");exit(0);}enableProfiling_=true;profileThreshold_=TimeDuration::FromMilliseconds(atoi(env));}}Statistics::~Statistics(){if(fp&&fp!=stdout&&fp!=stderr)fclose(fp);}/* static */boolStatistics::initialize(){#ifdef DEBUG// Sanity check generated tables.for(autoi:AllPhases()){autoparent=phases[i].parent;if(parent!=Phase::NONE){MOZ_ASSERT(phases[i].depth==phases[parent].depth+1);}autofirstChild=phases[i].firstChild;if(firstChild!=Phase::NONE){MOZ_ASSERT(i==phases[firstChild].parent);MOZ_ASSERT(phases[i].depth==phases[firstChild].depth-1);}autonextSibling=phases[i].nextSibling;if(nextSibling!=Phase::NONE){MOZ_ASSERT(parent==phases[nextSibling].parent);MOZ_ASSERT(phases[i].depth==phases[nextSibling].depth);}autonextInPhase=phases[i].nextInPhase;if(nextInPhase!=Phase::NONE){MOZ_ASSERT(phases[i].phaseKind==phases[nextInPhase].phaseKind);MOZ_ASSERT(parent!=phases[nextInPhase].parent);}}for(autoi:AllPhaseKinds()){MOZ_ASSERT(phases[phaseKinds[i].firstPhase].phaseKind==i);for(autoj:AllPhaseKinds()){MOZ_ASSERT_IF(i!=j,phaseKinds[i].telemetryBucket!=phaseKinds[j].telemetryBucket);}}#endifreturntrue;}JS::GCSliceCallbackStatistics::setSliceCallback(JS::GCSliceCallbacknewCallback){JS::GCSliceCallbackoldCallback=sliceCallback;sliceCallback=newCallback;returnoldCallback;}JS::GCNurseryCollectionCallbackStatistics::setNurseryCollectionCallback(JS::GCNurseryCollectionCallbacknewCallback){autooldCallback=nurseryCollectionCallback;nurseryCollectionCallback=newCallback;returnoldCallback;}TimeDurationStatistics::clearMaxGCPauseAccumulator(){TimeDurationprior=maxPauseInInterval;maxPauseInInterval=0;returnprior;}TimeDurationStatistics::getMaxGCPauseSinceClear(){returnmaxPauseInInterval;}// Sum up the time for a phase, including instances of the phase with different// parents.staticTimeDurationSumPhase(PhaseKindphaseKind,constStatistics::PhaseTimeTable×){TimeDurationsum=0;for(Phasephase=phaseKinds[phaseKind].firstPhase;phase!=Phase::NONE;phase=phases[phase].nextInPhase){sum+=times[phase];}returnsum;}staticvoidCheckSelfTime(Phaseparent,Phasechild,constStatistics::PhaseTimeTable×,constStatistics::PhaseTimeTable&selfTimes,TimeDurationchildTime){if(selfTimes[parent]<childTime){fprintf(stderr,"Parent %s time = %.3fms with %.3fms remaining, child %s time %.3fms\n",phases[parent].name,times[parent].ToMilliseconds(),selfTimes[parent].ToMilliseconds(),phases[child].name,childTime.ToMilliseconds());fflush(stderr);MOZ_CRASH();}}staticPhaseKindLongestPhaseSelfTime(constStatistics::PhaseTimeTable×){// Start with total times per expanded phase, including children's times.Statistics::PhaseTimeTableselfTimes(times);// We have the total time spent in each phase, including descendant times.// Loop over the children and subtract their times from their parent's self// time.for(autoi:AllPhases()){Phaseparent=phases[i].parent;if(parent!=Phase::NONE){CheckSelfTime(parent,i,times,selfTimes,times[i]);selfTimes[parent]-=times[i];}}// Sum expanded phases corresponding to the same phase.EnumeratedArray<PhaseKind,PhaseKind::LIMIT,TimeDuration>phaseTimes;for(autoi:AllPhaseKinds())phaseTimes[i]=SumPhase(i,selfTimes);// Loop over this table to find the longest phase.TimeDurationlongestTime=0;PhaseKindlongestPhase=PhaseKind::NONE;for(autoi:AllPhaseKinds()){if(phaseTimes[i]>longestTime){longestTime=phaseTimes[i];longestPhase=i;}}returnlongestPhase;}voidStatistics::printStats(){if(aborted){fprintf(fp,"OOM during GC statistics collection. The report is unavailable for this GC.\n");}else{UniqueCharsmsg=formatDetailedMessage();if(msg){doublesecSinceStart=(slices_[0].start-TimeStamp::ProcessCreation()).ToSeconds();fprintf(fp,"GC(T+%.3fs) %s\n",secSinceStart,msg.get());}}fflush(fp);}voidStatistics::beginGC(JSGCInvocationKindkind){slices_.clearAndFree();sccTimes.clearAndFree();gckind=kind;nonincrementalReason_=gc::AbortReason::None;preBytes=runtime->gc.usage.gcBytes();startingMajorGCNumber=runtime->gc.majorGCCount();}voidStatistics::endGC(){TimeDurationsccTotal,sccLongest;sccDurations(&sccTotal,&sccLongest);runtime->addTelemetry(JS_TELEMETRY_GC_IS_ZONE_GC,!zoneStats.isCollectingAllZones());TimeDurationmarkTotal=SumPhase(PhaseKind::MARK,phaseTimes);TimeDurationmarkRootsTotal=SumPhase(PhaseKind::MARK_ROOTS,phaseTimes);runtime->addTelemetry(JS_TELEMETRY_GC_MARK_MS,t(markTotal));runtime->addTelemetry(JS_TELEMETRY_GC_SWEEP_MS,t(phaseTimes[Phase::SWEEP]));if(runtime->gc.isCompactingGc()){runtime->addTelemetry(JS_TELEMETRY_GC_COMPACT_MS,t(phaseTimes[Phase::COMPACT]));}runtime->addTelemetry(JS_TELEMETRY_GC_MARK_ROOTS_MS,t(markRootsTotal));runtime->addTelemetry(JS_TELEMETRY_GC_MARK_GRAY_MS,t(phaseTimes[Phase::SWEEP_MARK_GRAY]));runtime->addTelemetry(JS_TELEMETRY_GC_NON_INCREMENTAL,nonincremental());if(nonincremental())runtime->addTelemetry(JS_TELEMETRY_GC_NON_INCREMENTAL_REASON,uint32_t(nonincrementalReason_));runtime->addTelemetry(JS_TELEMETRY_GC_INCREMENTAL_DISABLED,!runtime->gc.isIncrementalGCAllowed());runtime->addTelemetry(JS_TELEMETRY_GC_SCC_SWEEP_TOTAL_MS,t(sccTotal));runtime->addTelemetry(JS_TELEMETRY_GC_SCC_SWEEP_MAX_PAUSE_MS,t(sccLongest));if(!aborted){TimeDurationtotal,longest;gcDuration(&total,&longest);runtime->addTelemetry(JS_TELEMETRY_GC_MS,t(total));runtime->addTelemetry(JS_TELEMETRY_GC_MAX_PAUSE_MS,t(longest));runtime->addTelemetry(JS_TELEMETRY_GC_MAX_PAUSE_MS_2,t(longest));constdoublemmu50=computeMMU(TimeDuration::FromMilliseconds(50));runtime->addTelemetry(JS_TELEMETRY_GC_MMU_50,mmu50*100);}if(fp)printStats();// Clear the OOM flag.aborted=false;}voidStatistics::beginNurseryCollection(JS::gcreason::Reasonreason){count(STAT_MINOR_GC);startingMinorGCNumber=runtime->gc.minorGCCount();if(nurseryCollectionCallback){(*nurseryCollectionCallback)(TlsContext.get(),JS::GCNurseryProgress::GC_NURSERY_COLLECTION_START,reason);}}voidStatistics::endNurseryCollection(JS::gcreason::Reasonreason){if(nurseryCollectionCallback){(*nurseryCollectionCallback)(TlsContext.get(),JS::GCNurseryProgress::GC_NURSERY_COLLECTION_END,reason);}}voidStatistics::beginSlice(constZoneGCStats&zoneStats,JSGCInvocationKindgckind,SliceBudgetbudget,JS::gcreason::Reasonreason){this->zoneStats=zoneStats;boolfirst=!runtime->gc.isIncrementalGCInProgress();if(first)beginGC(gckind);if(!slices_.emplaceBack(budget,reason,TimeStamp::Now(),GetPageFaultCount(),runtime->gc.state())){// If we are OOM, set a flag to indicate we have missing slice data.aborted=true;return;}runtime->addTelemetry(JS_TELEMETRY_GC_REASON,reason);// Slice callbacks should only fire for the outermost level.boolwasFullGC=zoneStats.isCollectingAllZones();if(sliceCallback){JSContext*cx=TlsContext.get();JS::GCDescriptiondesc(!wasFullGC,false,gckind,reason);if(first)(*sliceCallback)(cx,JS::GC_CYCLE_BEGIN,desc);(*sliceCallback)(cx,JS::GC_SLICE_BEGIN,desc);}}voidStatistics::endSlice(){if(!aborted){auto&slice=slices_.back();slice.end=TimeStamp::Now();slice.endFaults=GetPageFaultCount();slice.finalState=runtime->gc.state();TimeDurationsliceTime=slice.end-slice.start;runtime->addTelemetry(JS_TELEMETRY_GC_SLICE_MS,t(sliceTime));runtime->addTelemetry(JS_TELEMETRY_GC_RESET,slice.wasReset());if(slice.wasReset())runtime->addTelemetry(JS_TELEMETRY_GC_RESET_REASON,uint32_t(slice.resetReason));if(slice.budget.isTimeBudget()){int64_tbudget_ms=slice.budget.timeBudget.budget;runtime->addTelemetry(JS_TELEMETRY_GC_BUDGET_MS,budget_ms);if(budget_ms==runtime->gc.defaultSliceBudget())runtime->addTelemetry(JS_TELEMETRY_GC_ANIMATION_MS,t(sliceTime));// Record any phase that goes more than 2x over its budget.if(sliceTime.ToMilliseconds()>2*budget_ms){reportLongestPhase(slice.phaseTimes,JS_TELEMETRY_GC_SLOW_PHASE);// If we spend a significant length of time waiting for parallel// tasks then report the longest task.TimeDurationjoinTime=SumPhase(PhaseKind::JOIN_PARALLEL_TASKS,slice.phaseTimes);if(joinTime.ToMilliseconds()>budget_ms)reportLongestPhase(slice.parallelTimes,JS_TELEMETRY_GC_SLOW_TASK);}}sliceCount_++;}boollast=!runtime->gc.isIncrementalGCInProgress();if(last)endGC();if(enableProfiling_&&!aborted&&slices_.back().duration()>=profileThreshold_)printSliceProfile();// Slice callbacks should only fire for the outermost level.if(!aborted){boolwasFullGC=zoneStats.isCollectingAllZones();if(sliceCallback){JSContext*cx=TlsContext.get();JS::GCDescriptiondesc(!wasFullGC,last,gckind,slices_.back().reason);(*sliceCallback)(cx,JS::GC_SLICE_END,desc);if(last)(*sliceCallback)(cx,JS::GC_CYCLE_END,desc);}}// Do this after the slice callback since it uses these values.if(last){for(auto&count:counts)count=0;// Clear the timers at the end of a GC, preserving the data for PhaseKind::MUTATOR.automutatorStartTime=phaseStartTimes[Phase::MUTATOR];automutatorTime=phaseTimes[Phase::MUTATOR];PodZero(&phaseStartTimes);PodZero(&phaseTimes);phaseStartTimes[Phase::MUTATOR]=mutatorStartTime;phaseTimes[Phase::MUTATOR]=mutatorTime;}}voidStatistics::reportLongestPhase(constPhaseTimeTable×,inttelemetryId){PhaseKindlongest=LongestPhaseSelfTime(times);if(longest==PhaseKind::NONE)return;uint8_tbucket=phaseKinds[longest].telemetryBucket;runtime->addTelemetry(telemetryId,bucket);}boolStatistics::startTimingMutator(){if(phaseStack.length()!=0){// Should only be called from outside of GC.MOZ_ASSERT(phaseStack.length()==1);MOZ_ASSERT(phaseStack[0]==Phase::MUTATOR);returnfalse;}MOZ_ASSERT(suspendedPhases.empty());timedGCTime=0;phaseStartTimes[Phase::MUTATOR]=TimeStamp();phaseTimes[Phase::MUTATOR]=0;timedGCStart=TimeStamp();beginPhase(PhaseKind::MUTATOR);returntrue;}boolStatistics::stopTimingMutator(double&mutator_ms,double&gc_ms){// This should only be called from outside of GC, while timing the mutator.if(phaseStack.length()!=1||phaseStack[0]!=Phase::MUTATOR)returnfalse;endPhase(PhaseKind::MUTATOR);mutator_ms=t(phaseTimes[Phase::MUTATOR]);gc_ms=t(timedGCTime);returntrue;}voidStatistics::suspendPhases(PhaseKindsuspension){MOZ_ASSERT(suspension==PhaseKind::EXPLICIT_SUSPENSION||suspension==PhaseKind::IMPLICIT_SUSPENSION);while(!phaseStack.empty()){MOZ_ASSERT(suspendedPhases.length()<MAX_SUSPENDED_PHASES);Phaseparent=phaseStack.back();suspendedPhases.infallibleAppend(parent);recordPhaseEnd(parent);}suspendedPhases.infallibleAppend(lookupChildPhase(suspension));}voidStatistics::resumePhases(){MOZ_ASSERT(suspendedPhases.back()==Phase::EXPLICIT_SUSPENSION||suspendedPhases.back()==Phase::IMPLICIT_SUSPENSION);suspendedPhases.popBack();while(!suspendedPhases.empty()&&suspendedPhases.back()!=Phase::EXPLICIT_SUSPENSION&&suspendedPhases.back()!=Phase::IMPLICIT_SUSPENSION){PhaseresumePhase=suspendedPhases.popCopy();if(resumePhase==Phase::MUTATOR)timedGCTime+=TimeStamp::Now()-timedGCStart;recordPhaseBegin(resumePhase);}}voidStatistics::beginPhase(PhaseKindphaseKind){// No longer timing these phases. We should never see these.MOZ_ASSERT(phaseKind!=PhaseKind::GC_BEGIN&&phaseKind!=PhaseKind::GC_END);// PhaseKind::MUTATOR is suspended while performing GC.if(currentPhase()==Phase::MUTATOR)suspendPhases(PhaseKind::IMPLICIT_SUSPENSION);recordPhaseBegin(lookupChildPhase(phaseKind));}voidStatistics::recordPhaseBegin(Phasephase){MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime));// Guard against any other re-entry.MOZ_ASSERT(!phaseStartTimes[phase]);MOZ_ASSERT(phaseStack.length()<MAX_PHASE_NESTING);Phasecurrent=currentPhase();MOZ_ASSERT(phases[phase].parent==current);TimeStampnow=TimeStamp::Now();if(current!=Phase::NONE)MOZ_RELEASE_ASSERT(now>=phaseStartTimes[currentPhase()]);phaseStack.infallibleAppend(phase);phaseStartTimes[phase]=now;}voidStatistics::recordPhaseEnd(Phasephase){MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime));MOZ_ASSERT(phaseStartTimes[phase]);TimeStampnow=TimeStamp::Now();MOZ_RELEASE_ASSERT(now>=phaseStartTimes[phase]);if(phase==Phase::MUTATOR)timedGCStart=now;phaseStack.popBack();TimeDurationt=now-phaseStartTimes[phase];if(!slices_.empty())slices_.back().phaseTimes[phase]+=t;phaseTimes[phase]+=t;phaseStartTimes[phase]=TimeStamp();}voidStatistics::endPhase(PhaseKindphaseKind){Phasephase=currentPhase();MOZ_ASSERT(phase!=Phase::NONE);MOZ_ASSERT(phases[phase].phaseKind==phaseKind);recordPhaseEnd(phase);// When emptying the stack, we may need to return to timing the mutator// (PhaseKind::MUTATOR).if(phaseStack.empty()&&!suspendedPhases.empty()&&suspendedPhases.back()==Phase::IMPLICIT_SUSPENSION){resumePhases();}}voidStatistics::recordParallelPhase(PhaseKindphaseKind,TimeDurationduration){Phasephase=lookupChildPhase(phaseKind);// Record the duration for all phases in the tree up to the root. This is// not strictly necessary but makes the invariant that parent phase times// include their children apply to both phaseTimes and parallelTimes.while(phase!=Phase::NONE){if(!slices_.empty())slices_.back().parallelTimes[phase]+=duration;parallelTimes[phase]+=duration;phase=phases[phase].parent;}}voidStatistics::endParallelPhase(PhaseKindphaseKind,constGCParallelTask*task){Phasephase=lookupChildPhase(phaseKind);phaseStack.popBack();if(!slices_.empty())slices_.back().phaseTimes[phase]+=task->duration();phaseTimes[phase]+=task->duration();phaseStartTimes[phase]=TimeStamp();}TimeStampStatistics::beginSCC(){returnTimeStamp::Now();}voidStatistics::endSCC(unsignedscc,TimeStampstart){if(scc>=sccTimes.length()&&!sccTimes.resize(scc+1))return;sccTimes[scc]+=TimeStamp::Now()-start;}/* * MMU (minimum mutator utilization) is a measure of how much garbage collection * is affecting the responsiveness of the system. MMU measurements are given * with respect to a certain window size. If we report MMU(50ms) = 80%, then * that means that, for any 50ms window of time, at least 80% of the window is * devoted to the mutator. In other words, the GC is running for at most 20% of * the window, or 10ms. The GC can run multiple slices during the 50ms window * as long as the total time it spends is at most 10ms. */doubleStatistics::computeMMU(TimeDurationwindow)const{MOZ_ASSERT(!slices_.empty());TimeDurationgc=slices_[0].end-slices_[0].start;TimeDurationgcMax=gc;if(gc>=window)return0.0;intstartIndex=0;for(size_tendIndex=1;endIndex<slices_.length();endIndex++){auto*startSlice=&slices_[startIndex];auto&endSlice=slices_[endIndex];gc+=endSlice.end-endSlice.start;while(endSlice.end-startSlice->end>=window){gc-=startSlice->end-startSlice->start;startSlice=&slices_[++startIndex];}TimeDurationcur=gc;if(endSlice.end-startSlice->start>window)cur-=(endSlice.end-startSlice->start-window);if(cur>gcMax)gcMax=cur;}returndouble((window-gcMax)/window);}voidStatistics::maybePrintProfileHeaders(){staticintprintedHeader=0;if((printedHeader++%200)==0){printProfileHeader();for(ZoneGroupsItergroup(runtime);!group.done();group.next()){if(group->nursery().enableProfiling()){Nursery::printProfileHeader();break;}}}}voidStatistics::printProfileHeader(){if(!enableProfiling_)return;fprintf(stderr,"MajorGC: Reason States ");fprintf(stderr," %6s","total");#define PRINT_PROFILE_HEADER(name, text, phase) \ fprintf(stderr, " %6s", text);FOR_EACH_GC_PROFILE_TIME(PRINT_PROFILE_HEADER)#undef PRINT_PROFILE_HEADERfprintf(stderr,"\n");}/* static */voidStatistics::printProfileTimes(constProfileDurations×){for(autotime:times)fprintf(stderr," %6"PRIi64,static_cast<int64_t>(time.ToMilliseconds()));fprintf(stderr,"\n");}voidStatistics::printSliceProfile(){constSliceData&slice=slices_.back();maybePrintProfileHeaders();fprintf(stderr,"MajorGC: %20s %1d -> %1d ",ExplainReason(slice.reason),int(slice.initialState),int(slice.finalState));ProfileDurationstimes;times[ProfileKey::Total]=slice.duration();totalTimes_[ProfileKey::Total]+=times[ProfileKey::Total];#define GET_PROFILE_TIME(name, text, phase) \ times[ProfileKey::name] = SumPhase(phase, slice.phaseTimes); \ totalTimes_[ProfileKey::name] += times[ProfileKey::name];FOR_EACH_GC_PROFILE_TIME(GET_PROFILE_TIME)#undef GET_PROFILE_TIMEprintProfileTimes(times);}voidStatistics::printTotalProfileTimes(){if(enableProfiling_){fprintf(stderr,"MajorGC TOTALS: %7"PRIu64" slices: ",sliceCount_);printProfileTimes(totalTimes_);}}